package com.xuecheng.user.service.impl;

import com.alibaba.fastjson.JSON;
import com.xuecheng.user.context.AuthServiceContext;
import com.xuecheng.user.mapper.XcUserMapper;
import com.xuecheng.user.model.dto.AuthParamsDTO;
import com.xuecheng.user.model.dto.XcUserExt;
import com.xuecheng.user.model.po.XcMenu;
import com.xuecheng.user.service.AuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @author 勾新杰
 * @version 1.0
 * @description 获取UserDetail交给DaoAuthenticationProvider校验密码，
 * 后期取消DaoAuthenticationProvider校验密码逻辑，
 * 自己用AuthService去认证操作，不同的认证方式（如用户名密码、短信验证码、微信）
 * 交给不同的AuthService实现实现类去处理，DaoAuthenticationProvider调用
 *  loadUserByUsername方法的时候会捕获异常并抛出异常InternalAuthenticationServiceException
 * @date 2022/9/28 18:09
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserDetailServiceImpl implements UserDetailsService {

    private final XcUserMapper userMapper;

    // private final ApplicationContext applicationContext;

    private final AuthServiceContext authServiceContext;

    /**
     * 根据用户名查询用户信息
     *
     * @param s 用户名
     * @return UserDetails
     * @throws UsernameNotFoundException 用户名不存在异常
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 1. 将认证参数username转为AuthParamsDto类型，password不会传，穿了也不会校验
        AuthParamsDTO authParamsDto;
        try {
            authParamsDto = JSON.parseObject(s, AuthParamsDTO.class);
        } catch (Exception e) {
            log.error("认证参数不符合项目要求:{}", e.getMessage());
            throw new RuntimeException("认证参数不符合项目要求");
        }
        // 2. 根据authType获取beanName，分发给指定的bean完成认证
        // String authType = authParamsDto.getAuthType();
        // String beanName = authType + "AuthService"; // 间接使用工厂模式，getBean就是工厂方法，根据beanName获取bean
        // AuthService authService = applicationContext.getBean(beanName, AuthService.class);
        // XcUserExt userExt = authService.auth(authParamsDto);

        // 优化为策略模式+工厂模式
        XcUserExt userExt = authServiceContext.auth(authParamsDto);
        // 3. 获取UserDetails并返回
        return getUserDetails(userExt);
    }

    /**
     * 根据用户信息获取UserDetails
     *
     * @param userExt 用户信息
     * @return UserDetails
     */
    private UserDetails getUserDetails(XcUserExt userExt) {
        // 1. 获取用户权限，并设置
        List<XcMenu> xcMenus = userMapper.selectPermissionByUserId(userExt.getId());
        List<String> permissions = xcMenus.stream().map(XcMenu::getCode).collect(Collectors.toList());
        userExt.setPermissions(permissions);
        // 2. 取出用户密码，并设置为null
        String password = userExt.getPassword();
        userExt.setPassword(null);
        // 3. 将用户信息转为json，并设置给username，password设置给password，authorities设置给authorities
        String userString = JSON.toJSONString(userExt); // userExt 包含该用户的权限，jwt令牌的负载部分本来是根据UserDetail类的username设置的，这里对username重写了，负载就是UserExt对象序列化的结果
        String[] authorities = permissions.isEmpty() ? new String[]{"null"} : permissions.toArray(new String[0]);
        return User.withUsername(userString).password(password).authorities(authorities).build();
    }
}
